<?php

namespace Ktnw\WechatSupport\Utils;

use Exception;
use Illuminate\Support\Arr;
use Ktnw\WechatSupport\Services\WeChatSupportService;

/**
 * 微信登录相关工具
 */
class WeChatUtils
{

    const WECHAT_ROOT = "https://open.weixin.qq.com";

    /**
     * 微信网页授权：step1: 用户同意授权，获取code.
     *
     * @param string $appId 微信公众号的appId
     * @param string $authRedirectUrl 用户授权后，微信端重定向的URL.该URL需在微信公众号的网页授权接口中进行设置。
     * @param string $authScope 应用授权作用域 snsapi_base （不弹出授权页面，直接跳转，只能获取用户openid），snsapi_userinfo （弹出授权页面，可通过openid拿到昵称、性别、所在地。并且， 即使在未关注的情况下，只要用户授权，也能获取其信息 ）
     * @param string $state 重定向后会带上state参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节
     * @return string
     */
    public static function getAuthorize(string $appId, string $authRedirectUrl, string $authScope = '', string $state = ''): string
    {
        $authScope       = empty($authScope) ? "snsapi_userinfo" : $authScope;
        $state           = empty($state) ? "" : urlencode($state);
        $authRedirectUrl = urlencode($authRedirectUrl);
        return self::getWeChatOpenDomainName() . "/connect/oauth2/authorize?appid=" . $appId . "&redirect_uri=" . $authRedirectUrl . "&response_type=code&scope=" . $authScope . "&state=" . $state . "#wechat_redirect";
    }


    /**
     * 微信网页授权：step2: 通过code换取网页授权access_token
     *
     * @param string $appId
     * @param string $appSecret
     * @param string $code
     * @return array
     * @throws Exception
     */
    public static function findAuthorizationAccessToken(string $appId, string $appSecret, string $code): array
    {
        $tokenInfo = "";
        $count     = 1;
        while ($count < 4 && empty($tokenInfo)) {
            $tokenInfo = self::findWeChatAccessToken($appId, $appSecret, $code);
            $count++;
        }
        return $tokenInfo;
    }

    /**
     * 用code获取网页授权登录的access_token
     * @param string $appId
     * @param string $appSecret
     * @param string $code
     * @return array
     * @throws Exception
     */
    private static function findWeChatAccessToken(string $appId, string $appSecret, string $code): array
    {
        $fetchAccessTokenUrl = self::getWeChatDomainName() . "/sns/oauth2/access_token?appid=" . $appId . "&secret=" . $appSecret . "&code=" . $code . "&grant_type=authorization_code";
        $r                   = HttpClient::sendHttp($fetchAccessTokenUrl);
        if (is_string($r)) {
            $r = json_decode($r, true);
        }
        $access_token = array_key_exists("access_token", $r) ? $r["access_token"] : "";
        $openid       = array_key_exists("openid", $r) ? $r["openid"] : "";
        return ['access_token' => $access_token, 'openid' => $openid];
    }

    /**
     * 网页授权登录拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $accessToken
     * @param string $openid
     * @return array
     * @throws Exception
     */
    public static function fetchWeChatUserInfo(string $accessToken, string $openid): array
    {
        $fetchUrl = self::getWeChatDomainName() . "/sns/userinfo?access_token=" . $accessToken . "&openid=" . $openid . "&lang=zh_CN";
        return HttpClient::sendHttp($fetchUrl, [], $method = 'POST');
    }

    /**
     * 获取有效的jsapi_ticket
     *
     * @param string $wxKey 微信标识
     * @param string $appId
     * @param string $appSecret
     * @return string
     * @throws Exception
     */
    public static function fetchValidWxJsapiTicket(string $wxKey, string $appId, string $appSecret): string
    {
        // 先从数据库中查询有效的jsapi_ticket
        $jsapiTicket = WeChatSupportService::findWxJsapiTicket($wxKey);
        if (empty($jsapiTicket)) {
            // 调用微信接口获取Jsapi_ticket
            $accessToken = self::fetchValidWxAccessToken($wxKey, $appId, $appSecret);
            if (empty($accessToken)) {
                return "";
            }
            $ticket      = self::getJsapiTicket($accessToken);
            $jsapiTicket = empty($ticket) ? '' : $ticket['ticket'];
            $expiresIn   = empty($ticket) ? '' : $ticket['expires_in'];
            if (!empty($jsapiTicket)) {
                // 保存jsapiTicket到数据库中
                WeChatSupportService::saveWxJsapiTicket($wxKey, $jsapiTicket, $expiresIn);
            }
        }
        return $jsapiTicket;
    }

    /**
     * 获取JsapiTicket
     * @param string $accessToken
     * @return array
     * @throws Exception
     */
    public static function getJsapiTicket(string $accessToken): array
    {
        $fetchUrl = self::getWeChatDomainName() . "/cgi-bin/ticket/getticket?access_token=" . $accessToken . "&type=jsapi ";
        $r        = file_get_contents($fetchUrl);
        return empty($r) ? [] : json_decode($r, true);
    }

    /**
     * 获取有效的普通access_token
     * @throws Exception
     */
    public static function fetchValidWxAccessToken(string $wxKey, string $appId, string $appSecret): string
    {
        // 从数据库中查询该微信公众号对应的有效accessToken
        $accessToken = WeChatSupportService::findWxAccessToken($wxKey);
        if (empty($accessToken)) {
            // 调用微信接口获取access_token
            $tokenInfo = self::fetchWxCommonAccessToken($appId, $appSecret);
            if (!empty($tokenInfo)) {
                $accessToken = Arr::get($tokenInfo, "access_token");
                $expiresIn   = Arr::get($tokenInfo, "expires_in"); // 有效时间: 单位秒
                if ($accessToken) {
                    // 保存到数据库中
                    WeChatSupportService::saveWxAccessToken($wxKey, $accessToken, $expiresIn);
                }
            }
        }
        return $accessToken;
    }

    /**
     * 获取微信jsapi的access_token
     * access_token每天获取上限:2000
     * access_token有效时间:7200秒
     *
     * @return  array {"access_token":"ACCESS_TOKEN","expires_in":7200}
     * @throws Exception
     */
    public static function fetchWxCommonAccessToken(string $appId, string $appSecret): array
    {
        $fetchUrl = self::getWeChatDomainName() . "/cgi-bin/token?grant_type=client_credential&appid=" . $appId . "&secret=" . $appSecret;
        return HttpClient::sendHttp($fetchUrl);
    }


    /**
     * 获取微信接口的域名
     */
    private static function getWeChatDomainName(): string
    {
        $domain = config("weChatConfig.api_domain_name");
        return empty($domain) ? self::WECHAT_ROOT : $domain;
    }

    /**
     * 获取微信接口的域名
     */
    private static function getWeChatOpenDomainName(): string
    {
        $domain = config("weChatConfig.open_domain_name");
        return empty($domain) ? self::WECHAT_ROOT : $domain;
    }


}